package com.minigame.chess.gobang.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.minigame.chess.gobang.entity.domain.Position;
import com.minigame.chess.gobang.entity.request.JudgeRequest;
import com.minigame.chess.gobang.entity.table.Cell;
import com.minigame.chess.gobang.entity.table.Player;
import com.minigame.chess.gobang.entity.table.Round;
import com.minigame.chess.gobang.enums.Color;
import com.minigame.chess.gobang.enums.Degree;
import com.minigame.chess.gobang.mapper.CellMapper;
import com.minigame.chess.gobang.mapper.PlayerMapper;
import com.minigame.chess.gobang.mapper.RoundMapper;
import com.minigame.chess.gobang.service.ChessService;
import com.minigame.common.ApiException;
import com.minigame.common.ErrorTemplateEnum;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

@Service
public class ChessServiceImpl implements ChessService {
    @Resource
    private PlayerMapper playerMapper;
    @Resource
    private RoundMapper roundMapper;
    @Resource
    private CellMapper cellMapper;

    @Override
    public void savePlayer(String name) {
        LambdaQueryWrapper<Player> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(Player::getName, name);
        boolean exists = playerMapper.exists(wrapper);
        if (exists) {
            throw new ApiException(ErrorTemplateEnum.EXIST_PLAYER, name);
        }
        Player player = new Player();
        player.setName(name);
        playerMapper.insert(player);
    }

    @Override
    public void saveRound(Integer[] players) {
        Round round = new Round();
        round.setPlayerId(players[0]);
        round.setOppositePlayerId(players[1]);
        roundMapper.insert(round);
    }

    @Override
    public boolean putAndWin(JudgeRequest judgeRequest) {
        insertCell(judgeRequest);
        Map<Position, Color> cellMap = getCellMap(judgeRequest.getRoundId(), judgeRequest.getColorCode());
        try {
            return win(new Position(judgeRequest.getX(), judgeRequest.getY()), Color.ofCode(judgeRequest.getColorCode()), cellMap);
        } catch (ExecutionException | InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    private void insertCell(JudgeRequest judgeRequest) {
        Cell cell = new Cell();
        cell.setRoundId(judgeRequest.getRoundId());
        cell.setColor(judgeRequest.getColorCode());
        cell.setX(judgeRequest.getX());
        cell.setY(judgeRequest.getY());
        cellMapper.insert(cell);
    }

    private Map<Position, Color> getCellMap(Integer roundId, Integer colorCode) {
        LambdaQueryWrapper<Cell> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(Cell::getRoundId, roundId);
        List<Cell> cells = cellMapper.selectList(wrapper);
        Map<Position, Color> cellMap = new HashMap<>();
        cells.forEach(c -> cellMap.put(new Position(c.getX(), c.getY()), Color.ofCode(colorCode)));
        return cellMap;
    }

    private boolean win(Position p, Color color, Map<Position, Color> cellMap) throws ExecutionException, InterruptedException {
        Degree[] degrees = Degree.values();
        BlockingQueue<CompletableFuture<Boolean>> queue = new ArrayBlockingQueue<>(degrees.length);
        for (Degree degree : degrees) {
            CompletableFuture<Boolean> f = CompletableFuture.supplyAsync(() -> p.hasLine(degree, cellMap, color));
            queue.add(f);
        }
        CompletableFuture<Boolean> f;
        boolean win = false;
        while ((f = queue.poll()) != null) {
            Boolean hasLine = f.get();
            if (hasLine) {
                win = true;
                continue;
            }
            if (win) {
                f.cancel(true);
            }
        }
        return win;
    }
}

























